package com.file.parser;

import com.file.bo.ResponseData;
import com.file.bo.mail.CITIC;
import com.file.bo.mail.CITICTran;
import com.file.constant.ErrorCode;
import com.file.parser.traprange.PDFTableExtractor;
import com.file.util.JsonUtils;
import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition;
import technology.tabula.*;
import technology.tabula.detectors.NurminenDetectionAlgorithm;
import technology.tabula.extractors.BasicExtractionAlgorithm;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
public class CITICPdfParser extends BasePdfParser {

    private static final Integer CITIC_PDF_HEADER_LINE_NUMBER = 9;
    private static final Integer CITIC_PDF_FOOTER_LINE_NUMBER = 14;

    public ResponseData<String> parseCITICPdfToJson(String daId, String filePath) {
        log.info("parseCITICPdfToJson started, daId:{}", daId);
        String json = null;

        try {
            CITIC icbc = parseCITICPdf(filePath);
            json = JsonUtils.convertObjectToJson(icbc);
        } catch (Exception e) {
            log.error("OnError|{}||{}|{}|{}|{}", ErrorCode.FILE_PARSE_EXCEPTION.getCode(), daId, "", "", "parseCITICPdfToJson failed", e);
            return new ResponseData<String>(json, ErrorCode.FILE_PARSE_EXCEPTION.getCode(),
                    ErrorCode.FILE_PARSE_EXCEPTION.getMsg());
        }

        log.info("parseCITICPdfToJson completed, daId:{}", daId);
        return new ResponseData<String>(json, ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg());
    }

    private CITIC parseCITICPdf(String filePath) {
        CITIC citic = parseCITICHeader(filePath);
        List<CITICTran> citicTrans;
        try {
            citicTrans = parseCITICTrans(filePath);
        } catch (Exception e) {
            citicTrans = parseCITICTrans1(filePath);
        }
        try {
            citicTrans = parseRecipient(filePath, citicTrans);
        } catch (Exception e) {
            log.warn("parseRecipient failed", e);
        }

        citic.setCiticTrans(citicTrans);

        return citic;
    }

    private CITIC parseCITICHeader(String filePath) {
        CITIC citic = new CITIC();
        String pdfHeaderText = parsePdfHeaderText(filePath);
        String accountName = pdfHeaderText.substring(pdfHeaderText.indexOf("户名：") + 3, pdfHeaderText.indexOf("证件类型"))
                .trim();
        String idType = pdfHeaderText.substring(pdfHeaderText.indexOf("证件类型：") + 5, pdfHeaderText.indexOf("证件号码"))
                .trim();
        String idNumber = pdfHeaderText
                .substring(pdfHeaderText.indexOf("证件号码：") + 5, pdfHeaderText.indexOf("Account name")).trim();
        String accountNo = pdfHeaderText.substring(pdfHeaderText.indexOf("账号：") + 3, pdfHeaderText.indexOf("时间段"))
                .trim();
        String inquiryPeriod = pdfHeaderText.substring(pdfHeaderText.indexOf("时间段：") + 4, pdfHeaderText.indexOf("开立日期"))
                .trim();
        String issuanceDate = pdfHeaderText
                .substring(pdfHeaderText.indexOf("开立日期：") + 5, pdfHeaderText.indexOf("Account number")).trim();
        String inquireTheMinimumLimit = pdfHeaderText
                .substring(pdfHeaderText.indexOf("查询最低限额：") + 7, pdfHeaderText.indexOf("币种")).trim();
        String currency = pdfHeaderText
                .substring(pdfHeaderText.indexOf("币种：") + 3, pdfHeaderText.indexOf("Inquire the minimum limit")).replaceAll("\\p{Zs}", "");
        citic.setAccountName(accountName);
        citic.setIdType(idType);
        citic.setIdNumber(idNumber);
        citic.setAccountNo(accountNo);
        citic.setInquiryPeriod(inquiryPeriod);
        citic.setIssuanceDate(issuanceDate);
        citic.setInquireTheMinimumLimit(inquireTheMinimumLimit);
        citic.setCurrency(currency);

        return citic;
    }

    private List<CITICTran> parseCITICTrans(String filePath) {
        List<CITICTran> citicTrans = new ArrayList<>();

        String transText = parseTransToText(filePath);
        if (Strings.isNullOrEmpty(transText)) {
            return citicTrans;
        }

        List<List<String>> oldFieldsList = parseTransTextToTranFieldsList(transText);
        // 转化为可修改列表
        List<List<String>> tranFieldsList = new ArrayList<>();
        for (List<String> sublist : oldFieldsList) {
            tranFieldsList.add(new ArrayList<>(sublist));
        }

        for (int i = 0; i < tranFieldsList.size(); i++) {
            List<String> strings = tranFieldsList.get(i);
            if (StringUtils.containsAny(strings.get(0), "交易日期", "Transaction", "date", "声明")
                    || StringUtils.contains(strings.get(3), "Account paid")) {
                continue;
            }
            if (strings.size() == 8) {
                if (StringUtils.isNotBlank(strings.get(0))) {
                    CITICTran citicTran = new CITICTran();
                    citicTran.setTransactionDate(strings.get(0));
                    citicTran.setAccountSeqNo(strings.get(1));
                    citicTran.setAccountReceivable(strings.get(2));
                    citicTran.setAccountPaid(strings.get(3));
                    citicTran.setAccountBalance(strings.get(4));
                    citicTran.setDescription(strings.get(5));
                    citicTran.setRecipient(strings.get(6));
                    citicTran.setReversalFlag(strings.get(7));
                    citicTrans.add(citicTran);
                }
            }
            // 账户余额交易摘要 粘到一起了
            else if (strings.size() == 7) {
                if (StringUtils.isNotBlank(strings.get(0))) {
                    CITICTran citicTran = new CITICTran();
                    citicTran.setTransactionDate(strings.get(0));
                    citicTran.setAccountSeqNo(strings.get(1));
                    citicTran.setAccountReceivable(strings.get(2));
                    citicTran.setAccountPaid(strings.get(3));
                    parseAccountBalanceAndDescription(strings.get(4), citicTran);
                    citicTran.setRecipient(strings.get(5));
                    citicTran.setReversalFlag(strings.get(6));
                    citicTrans.add(citicTran);
                }
            }
            // Added 250523: 粘连到四个：交易日期账户序号收入金额支出金额账户余额、交易摘要、对方户名、被冲账标识
            else if (strings.size() == 4) {
                if (StringUtils.isNotBlank(strings.get(0))) {
                    CITICTran citicTran = new CITICTran();

                    String baseString = strings.get(0);
                    // parse all. 注意货币不一定是RMB；空格可能1~多个；小数位数可能1~多个
                    Pattern pattern = Pattern.compile("(\\d{8})(\\d+)([A-Z]+\\s*\\d+\\.\\d+)([A-Z]+\\s*\\d+\\.\\d+)");
                    Matcher matcher = pattern.matcher(baseString);
                    if (matcher.find()) {
                        citicTran.setTransactionDate(matcher.group(1));
                        citicTran.setAccountSeqNo(matcher.group(2));
                        String fundInOrOut = matcher.group(3);
                        String balance = matcher.group(4);
                        citicTran.setAccountBalance(balance);

                        // 和上一笔交易对比，判断是入账还是出账
                        if (!citicTrans.isEmpty()) {
                            CITICTran lastCiticTran = citicTrans.get(citicTrans.size() - 1);
                            String lastBalance = lastCiticTran.getAccountBalance();
                            // 提取数值进行对比
                            double lastBalanceAmount = Double.parseDouble(lastBalance.trim().split("\\s+")[1]);
                            double thisBalanceAmount = Double.parseDouble(balance.trim().split("\\s+")[1]);
                            if (lastBalanceAmount > thisBalanceAmount) {
                                citicTran.setAccountPaid(fundInOrOut);
                                citicTran.setAccountReceivable("");
                            } else {
                                citicTran.setAccountReceivable(fundInOrOut);
                                citicTran.setAccountPaid("");
                            }
                        } else {
                            // HARDCODE: 默认将所有流水的第一笔设置为出账
                            citicTran.setAccountPaid(fundInOrOut);
                            citicTran.setAccountReceivable("");
                        }
                    } else {
                        continue;
                    }

                    citicTran.setDescription(strings.get(1));
                    citicTran.setRecipient(strings.get(2));
                    citicTran.setReversalFlag(strings.get(3));
                    citicTrans.add(citicTran);
                } else {
                    // 处理跨行，将交易对手方放到下一行处理
                    String partialRecipient = strings.get(2);
                    if (StringUtils.isNotBlank(partialRecipient)) { //NOSONAR
                        if ((i + 1) < tranFieldsList.size()) { //NOSONAR
                            List<String> nextStrings = tranFieldsList.get(i + 1);
                            String nextRecipient = nextStrings.get(2);
                            nextStrings.set(2, partialRecipient + nextRecipient);
                            tranFieldsList.set(i + 1, nextStrings);
                        }
                    }
                }
            }
            // Added 250523: 粘连到四个：交易日期账户序号收入金额支出金额账户余额、交易摘要、对方户名、被冲账标识
            else if (strings.size() == 5) {
                if (StringUtils.isNotBlank(strings.get(0))) {
                    CITICTran citicTran = new CITICTran();

                    String baseString = strings.get(0);
                    String balance = strings.get(1);
                    // parse all. 形如："20241122000001RMB 25.61" 注意货币不一定是RMB；空格可能1~多个；小数位数可能1~多个
                    Pattern pattern = Pattern.compile("(\\d{8})(\\d+)([A-Z]+\\s*\\d+\\.\\d+)");
                    Matcher matcher = pattern.matcher(baseString);
                    if (matcher.find()) {
                        citicTran.setTransactionDate(matcher.group(1));
                        citicTran.setAccountSeqNo(matcher.group(2));
                        String fundInOrOut = matcher.group(3);
                        citicTran.setAccountBalance(balance);

                        // 和上一笔交易对比，判断是入账还是出账
                        if (!citicTrans.isEmpty()) {
                            CITICTran lastCiticTran = citicTrans.get(citicTrans.size() - 1);
                            String lastBalance = lastCiticTran.getAccountBalance();
                            // 提取数值进行对比
                            double lastBalanceAmount = Double.parseDouble(lastBalance.trim().split("\\s+")[1]);
                            double thisBalanceAmount = Double.parseDouble(balance.trim().split("\\s+")[1]);
                            if (lastBalanceAmount > thisBalanceAmount) {
                                citicTran.setAccountPaid(fundInOrOut);
                                citicTran.setAccountReceivable("");
                            } else {
                                citicTran.setAccountReceivable(fundInOrOut);
                                citicTran.setAccountPaid("");
                            }
                        } else {
                            // HARDCODE: 默认将所有流水的第一笔设置为出账
                            citicTran.setAccountPaid(fundInOrOut);
                            citicTran.setAccountReceivable("");
                        }
                    } else {
                        continue;
                    }

                    citicTran.setDescription(strings.get(2));
                    citicTran.setRecipient(strings.get(3));
                    citicTran.setReversalFlag(strings.get(4));
                    citicTrans.add(citicTran);
                } else {
                    // 处理跨行，将交易对手方放到下一行处理
                    String partialRecipient = strings.get(3);
                    if (StringUtils.isNotBlank(partialRecipient)) {
                        if ((i + 1) < tranFieldsList.size()) {
                            List<String> nextStrings = tranFieldsList.get(i + 1);
                            String nextRecipient = nextStrings.get(3);
                            nextStrings.set(3, partialRecipient + nextRecipient);
                            tranFieldsList.set(i + 1, nextStrings);
                        }
                    }
                }
            }
        }

        return citicTrans;
    }

    private void parseAccountBalanceAndDescription(String text, CITICTran citicTran) {
        Pattern pattern = Pattern.compile("[A-Z]{2,5}\\s*\\d{1,9}.\\d{2}");
        Matcher matcher = pattern.matcher(text.trim());
        if (matcher.find()) {
            citicTran.setAccountBalance(matcher.group());
            citicTran.setDescription(text.substring(matcher.end()));
        } else {
            citicTran.setDescription(text);
        }
    }

    private String parseTransToText(String filePath) {
        PDFTableExtractor extractor = (new PDFTableExtractor()).setSource(filePath);
        int pdfPageNumber = getPdfPageNumber(filePath);
        // 去掉第一页的Header，
        int[] skipFirstPageHeaderLinesIndexes = new int[CITIC_PDF_HEADER_LINE_NUMBER];
        for (int i = 0; i < CITIC_PDF_HEADER_LINE_NUMBER; i++) {
            skipFirstPageHeaderLinesIndexes[i] = i;
        }
        extractor.exceptLine(0, skipFirstPageHeaderLinesIndexes);

        // 去掉Footer
        if (pdfPageNumber == 1) {
            int[] skipLastPageFooterLinesIndexes = new int[CITIC_PDF_FOOTER_LINE_NUMBER - 1];
            for (int j = 0; j < CITIC_PDF_FOOTER_LINE_NUMBER - 1; j++) {
                skipLastPageFooterLinesIndexes[j] = -(j + 1);
            }
            extractor.exceptLine(0, skipLastPageFooterLinesIndexes);
        } else {
            // 去掉非最后一页的Footer
            for (int i = 1; i < pdfPageNumber; i++) {
                int[] skipLinesInNoneLastPage = {-1};
                extractor.exceptLine(i - 1, skipLinesInNoneLastPage);
            }

            // 去掉最后一页的Footer,两种情况
            int lineNumberInLastPdf = getLineNumberInLastPage(filePath, pdfPageNumber);
            // 1.footer文本行完整展现在最后一页PDF,则直接排除最后一页的footer文本行
            if (lineNumberInLastPdf >= CITIC_PDF_FOOTER_LINE_NUMBER) {
                int[] skipLastPageFooterLinesIndexes = new int[CITIC_PDF_FOOTER_LINE_NUMBER];
                for (int j = 0; j < CITIC_PDF_FOOTER_LINE_NUMBER; j++) {
                    skipLastPageFooterLinesIndexes[j] = -(j + 1);
                }
                extractor.exceptLine(pdfPageNumber - 1, skipLastPageFooterLinesIndexes);
            } else {
                // 2.footer文本行拆分在倒数第一页和倒数第二页，则直接排除最后一页和倒数第二页的WECHAT_FOOTER_LINE_NO-lineNumberInLastPdf
                extractor.exceptPage(pdfPageNumber - 1);

                // 由于第一页和倒数第二页都包含了页码, 而CITIC_PDF_FOOTER_LINE_NUMBER只计算了footer没有跨页的行数,
                // 所以此时要排除掉的FOOTER_LINE_NUMBER应该是 (CITIC_PDF_FOOTER_LINE_NUMBER + 1)
                int footerLinesNumberInlastSecondPage = (CITIC_PDF_FOOTER_LINE_NUMBER + 1) - lineNumberInLastPdf;
                int[] skipLastSecondPageFooterLinesIndexes = new int[footerLinesNumberInlastSecondPage];
                for (int k = 0; k < footerLinesNumberInlastSecondPage; k++) {
                    skipLastSecondPageFooterLinesIndexes[k] = -(k + 1);
                }
                extractor.exceptLine(pdfPageNumber - 2, skipLastSecondPageFooterLinesIndexes);
            }
        }

        String text = extractPdfToText(extractor);
        return text;
    }

    public List<CITICTran> parseRecipient(String filePath, List<CITICTran> citicTrans) {
        String pdfText = getPdfTextByStripper(filePath);
        pdfText = pdfText.replace(System.getProperty("line.separator", "\n"), " ");

        // 此处一行的格式2021-09-01 000001 RMB 650.00 RMB 6.30
        Pattern pattern1 = Pattern
                .compile("\\d{4}-\\d{2}-\\d{2}\\s\\d{6}\\s\\S{1,5}\\s\\d{1,9}.\\d{2}\\s\\S{1,5}\\s\\d{1,9}.\\d{2}");
        Matcher matcher1 = pattern1.matcher(pdfText);

        // 每行交易记录的第一个字所在的index
        int tranIndex = 0;
        Map<Integer, Integer> tranTextStartIndexMap = new HashMap<>();

        while (matcher1.find()) {
            // log.info(
            // 		"Match \"" + matcher1.group() + "\" at positions " + matcher1.start() + "-" + (matcher1.end() - 1));
            tranTextStartIndexMap.put(tranIndex, matcher1.start());
            tranIndex++;
        }

        // 找出每行交易记录的文本
        List<String> tranTextList = new ArrayList<>();
        for (int i = 1; i < tranTextStartIndexMap.size(); i++) {
            String tranText = pdfText.substring(tranTextStartIndexMap.get(i - 1), tranTextStartIndexMap.get(i));
            if (tranText.contains("交易日期")) {
                tranText = tranText.substring(0, tranText.indexOf("/") - 4).trim();
            }
            log.info(tranText);
            tranTextList.add(tranText);
        }

        String lastTranText = pdfText.substring(tranTextStartIndexMap.get(tranTextStartIndexMap.size() - 1));
        lastTranText = lastTranText.substring(0, lastTranText.indexOf("声明：")).trim();
        tranTextList.add(lastTranText);

        // 找出每行交易里的对手信息的值
        // 此处一行除去对手户名的格式 2021-09-14 000001 RMB 50.00 RMB 52.15 支付宝
        Pattern pattern2 = Pattern
                .compile("\\d{4}-\\d{2}-\\d{2}\\s\\d{6}\\s\\S{1,5}\\s\\d{1,9}.\\d{2}\\s\\S{1,5}\\s\\d{1,9}.\\d{2}\\s\\S{0,20}");
        for (int i = 0; i < citicTrans.size(); i++) {
            Matcher matcher2 = pattern2.matcher(tranTextList.get(i));
            while (matcher2.find()) {
                // 对方户名 + 被冲账标识(N),
                String recipient = tranTextList.get(i).substring(matcher2.end()).trim();
                // 被冲账标识占最后一个字符位置
                recipient = recipient.substring(0, recipient.length() - 1).trim();
                citicTrans.get(i).setRecipient(recipient.replace(" ", ""));
            }
        }

        return citicTrans;
    }

    private List<CITICTran> parseCITICTrans1(String filePath) {
        List<CITICTran> citicTrans = new ArrayList<>();

        List<List<String>> tranFieldsList = parseFileToRowList(filePath);
        for (int i = 0; i < tranFieldsList.size(); i++) {
            List<String> row = tranFieldsList.get(i);
            if (!row.get(0).matches("\\d+")) {
                continue;
            }
            if (row.size() == 8) {
                CITICTran citicTran = new CITICTran();
                citicTran.setTransactionDate(row.get(0));
                citicTran.setAccountSeqNo(row.get(1));
                citicTran.setAccountReceivable(row.get(2));
                citicTran.setAccountPaid(row.get(3));
                citicTran.setAccountBalance(row.get(4));
                citicTran.setDescription(row.get(5));
                // 对方户名换行
                if (i > 1 && StringUtils.isBlank(tranFieldsList.get(i - 1).get(0)) && StringUtils.isNotBlank(tranFieldsList.get(i - 1).get(6))) {
                    citicTran.setRecipient(tranFieldsList.get(i - 1).get(6) + row.get(6));
                } else {
                    citicTran.setRecipient(row.get(6));
                }
                citicTran.setReversalFlag(row.get(7));
                citicTrans.add(citicTran);

            }
        }
        return citicTrans;
    }

    private List<List<String>> parseFileToRowList(String filePath) {
        //页面所有table的记录，每一个元素代表下面一条记录cellList
        List<List<String>> rowList = new ArrayList<>();

        // 1. 读取文件
        File pdf = new File(filePath);

        // 2. pdfbox读取PDDocument
        try (PDDocument pdfDocument = PDDocument.load(pdf)) {

            // 3. tabula新建ObjectExtractor和NurminenDetectionAlgorithm，同时准备接收表格Rectangle的结构
            ObjectExtractor objectExtractor = new ObjectExtractor(pdfDocument);
            NurminenDetectionAlgorithm detectionAlgorithm = new NurminenDetectionAlgorithm();
            Map<Integer, List<Rectangle>> detectedTables = new HashMap<>();

            // 4. 获取每页的PageIterator
            PageIterator pages = objectExtractor.extract();

            // 5. 解析每页的Rectangle(table的位置)
            while (pages.hasNext()) {
                Page page = pages.next();
                List<Rectangle> tablesOnPage = detectionAlgorithm.detect(page);
                if (tablesOnPage.size() > 0) {
                    detectedTables.put(page.getPageNumber(), tablesOnPage);
                }
            }

            // 6.通过table位置获取表格具体内容
            BasicExtractionAlgorithm bea = new BasicExtractionAlgorithm();

            // 如果单页pdf中有多个表格，则解析出来的List<Rectangle>会有多个对象
            for (Map.Entry<Integer, List<Rectangle>> entry : detectedTables.entrySet()) {
                Page page = objectExtractor.extract(entry.getKey());
                // 默认每页只有一个表格，因此获取第0个rectangle/
                Rectangle rectangle = null; //NOSONAR
                List<Rectangle> rectangleList = entry.getValue();
                for (Rectangle r : rectangleList) {
                    double rectangleHeight = r.getHeight();
                    if (Double.compare(rectangleHeight, 0) > 0) {
                        rectangle = r;
                        break;
                    }
                }
                if (rectangle == null) {
                    throw new RuntimeException("rectangle is null.");
                }
                if (page.getPageNumber() == detectedTables.size()) {
                    float bottom = findTextBottom(filePath, page.getPageNumber(), "声明：");
                    rectangle.setBottom(bottom - 20);
                } else {
                    rectangle.setBottom(rectangle.getBottom() + 700);
                }


                Page area = page.getArea(rectangle);
                List<Table> tableList = null;
                try {
                    tableList = bea.extract(area);
                } catch (Exception e) {
                    log.info("rectangle extract table fail.");
                    continue;
                }
                // 如果每页有多个表格，解析每一个table
                for (Table table : tableList) {
                    for (int i = 0; i < table.getRowCount(); i++) {
                        List<String> cellList = new ArrayList<>();//一条记录，每个单元格占一个元素
                        for (int j = 0; j < table.getColCount(); j++) {
                            cellList.add(table.getCell(i, j).getText(false));
                        }
                        rowList.add(cellList);
                    }
                }

            }
            return rowList;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    static float findTextBottom(String pdfPath, int pageIndex, String searchText) throws IOException {
        float[] y = new float[1];
        try (PDDocument document = PDDocument.load(new File(pdfPath))) {
            PDFTextStripper stripper = new PDFTextStripper() {
                @Override
                protected void writeString(String text, List<TextPosition> textPositions) {
                    StringBuilder sb = new StringBuilder();
                    for (TextPosition tp : textPositions) {
                        sb.append(tp.getUnicode());
                    }
                    if (sb.toString().contains(searchText)) {
                        TextPosition tp = textPositions.get(0);
                        y[0] = tp.getY();
                    }
                }
            };
            stripper.setSortByPosition(true);
            stripper.setStartPage(pageIndex);
            stripper.setEndPage(pageIndex);
            stripper.getText(document);
        }
        return y[0];
    }

    public static void main(String[] args) {
        CITICPdfParser citicPdfParser = new CITICPdfParser();
        CITIC citic = new CITIC();
//        citic = citicPdfParser.parseCITICPdf("D:\\data\\file\\beehive-citicb\\zd4nstlc1936997092818202624_896e0d44e1f632d16c677e6b7c0fd331_beehive-citicb_jyls-0.pdf");
//        String json = JsonUtils.convertObjectToJson(citic);
//        System.out.println(json);

//        citic = citicPdfParser.parseCITICPdf("D:\\data\\file\\beehive-citicb\\zd4nstlc1939006033839730688_dbcf5cd40bef41515b134fdaea901a87_beehive-citicb_jyls-0.pdf");
//        System.out.println(JsonUtils.convertObjectToJson(citic));

        citic = citicPdfParser.parseCITICPdf("D:\\data\\file\\beehive-citicb\\bh2qnuzx1925372726872674304_b53434887e1957ca33df9cb634a4585d_beehive-citicb_jyls-0.pdf");
        System.out.println(JsonUtils.convertObjectToJson(citic));

    }

}
